package ru.urfc.portal.managers;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnectionFactory;
import javax.jms.Session;

import org.apache.commons.fileupload.FileItem;

import ru.urfc.portal.Utils;
import ru.urfc.portal.dao.AttachedFileDao;
import ru.urfc.portal.dao.filter.Filter;
import ru.urfc.portal.dao.filter.Operator;
import ru.urfc.portal.exceptions.InternalException;
import ru.urfc.portal.model.Client;
import ru.urfc.portal.model.documents.AttachedFile;
import ru.urfc.portal.model.documents.Document;
import ru.urfc.portal.model.documents.DocumentCreate;
import ru.urfc.portal.model.documents.DocumentState;
import ru.urfc.portal.model.documents.DocumentStateChange;
import ru.urfc.portal.model.documents.DocumentType;
import ru.urfc.portal.model.documents.DocumentUpdate;
import ru.urfc.portal.model.radio.DeviceWithNet;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DocumentManager extends BaseManager {

	@EJB DeviceManager deviceManager;
	@EJB AttachedFileDao attachedFileDao;
	@Resource(mappedName = "dmsQueue") Queue queue;
	@Resource(mappedName = "dmsConnFactory") QueueConnectionFactory queueConnectionFactory;
		
	public List<Document> getDeviceDocuments(Long clientId, Long deviceId) {
		return null;
	}

	public List<Document> getClientDocuments(Long clientId, Filter filter) {
		return find(Document.class, new Filter(filter).addEqual("client.id", clientId));
	}
	
	public Document createDocument(Long clientId, DocumentCreate documentCreate) {
		final Client client = findById(Client.class, clientId);
		final DocumentType type = exactlyOne(find(DocumentType.class, "code", documentCreate.getType().getCode()));
		final DocumentState state = exactlyOne(find(DocumentState.class, "code", DocumentState.DRAFT_CODE));
		final Document document = new Document(client, type, state);
		return persist(document);
	}

	/**
	 *	Load document with fetched data    	
	 */
	public Document getDocumentWithExtra(Long documentId) {
		Document document = findById(Document.class, documentId);
		document.getData();// fetch
		return document;		
	}
	
	//TODO test in EjbTest
	public Document getDocument(Long clientId, Long documentId) {
		return getDocumentWithExtra(documentId).check(clientId);
	}

	public Document upadteDocument(Long clientId, Long documentId, DocumentUpdate documentUpdate) {
		Document document = getDocument(clientId, documentId);
		document.checkEditable();
		return document.update(documentUpdate);
	}

	public Document updateAndSubmitDocument(Long clientId, Long documentId, DocumentUpdate documentUpdate) {
		Document document = upadteDocument(clientId, documentId, documentUpdate);
		enqueueDocumentId(document.getId());
		document.setState(exactlyOne(find(DocumentState.class, "code", DocumentState.SUBMITTED_CODE)));
		return document;
	}

	private void enqueueDocumentId(Long documentId) {
		try {
			Connection connection = queueConnectionFactory.createConnection();
			Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
			ObjectMessage message = session.createObjectMessage();
			message.setObject(documentId);
			MessageProducer producer = session.createProducer(queue);
			producer.send(message);			
		} catch (JMSException e) {
			throw new RuntimeException(e.getMessage(), e);
		}		
	}

	public List<DeviceWithNet> getSelectedDevices(Long clientId, Long documentId) {
		Document document = getDocument(clientId, documentId); // permission check
		return getSelectedDevices(document);
	}
	
	protected List<DeviceWithNet> getSelectedDevices(Document document) {
		Filter filter = new Filter("id", Operator.IN_LIST, document.getDeviceIdsAsList());
		return deviceManager.getDevices(document.getClient().getId(), filter);
	}

	public Document getDocumentWithSelectedDevices(Long documentId) {
		final Document document = getDocumentWithExtra(documentId);
		document.setSelectedDevices(getSelectedDevices(document));
		return document;
	}

	public List<DocumentStateChange> getDocumentStateChanges(Long clientId, Long documentId) {
		return find(DocumentStateChange.class, createFilter(clientId, documentId));
	}

	public List<AttachedFile> getAttachedFiles(Long clientId, Long documentId) {
		return find(AttachedFile.class, createFilter(clientId, documentId));
	}

	private Filter createFilter(Long clientId, Long documentId) {
		Filter filter = new Filter("document.id", documentId); 
		filter.addEqual("document.client.id", clientId);//check permissions
		return filter;
	}

	static public class AttachFileException extends InternalException {
		public AttachFileException(Throwable cause, String messageTemplate, Object... arguments) {
			super(cause, messageTemplate, arguments);
		}
	}
	
	public void attachFiles(Long clientId, Long documentId, List<FileItem> files) {
		Document document = getDocument(clientId, documentId);//check permissions
		document.checkEditable();
		for (FileItem fileItem: files) {
			try {
				Long fileSize = fileItem.getSize();
				AttachedFile attachedFile = oneOrNull(find(AttachedFile.class,
						new Filter("document.id", documentId).addEqual("name", fileItem.getName())));
				if (attachedFile == null) {
					attachedFile = persist(new AttachedFile(document, fileItem.getName(), ""));
				}
				InputStream inputStream = fileItem.getInputStream();
				attachedFileDao.writeContent(attachedFile, inputStream, fileSize.intValue());
				inputStream.close();
			} catch (IOException e) {
				throw new AttachFileException(e, "Unable read file ''{0}'' from stream ", fileItem.getName());
			}
		}
	}

	public void getAttachedFileContent(Long clientId, Long documentId, String fileName, OutputStream outputStream) throws IOException {
		AttachedFile file = getFile(clientId, documentId, fileName);
		InputStream inputStream = attachedFileDao.readContent(file);
		Utils.copyStream(inputStream, outputStream);
		inputStream.close();
		outputStream.close();
	}

	public void deleteFile(Long clientId, Long documentId, String fileName) {
		Document document = getDocument(clientId, documentId);//check permissions
		document.checkEditable();
		delete(getFile(clientId, documentId, fileName));
	}

	private AttachedFile getFile(Long clientId, Long documentId, String fileName) {
		return exactlyOne(find(AttachedFile.class, new Filter("name", fileName).
				addEqual("document.id", documentId). 
				addEqual("document.client.id", clientId)));// check permissions
	}
	
}
